﻿using System.Collections.Generic;
using System.Linq;
using LinFu.IoC.Interfaces;
using Moq;
namespace LinFu.Ioc.MoqIntegration
{
    /// <summary>
    /// Contains extensions methods to integrate the Moq mocking library with the LinFu.Ioc framework.
    /// </summary>
    /// <seealso cref="http://code.google.com/p/moq/"/>
    public static class ContainerExtensions
    {
        private static readonly IList<MockServiceInfo> _mockedServices = new List<MockServiceInfo>();

        /// <summary>
        /// Creates a new <see cref="Mock{T}"/> instance and instructs the <see cref="IServiceContainer"/> 
        /// to return the <see cref="Mock.Object"/> instead of the actual service instance.
        /// </summary>
        /// <typeparam name="T">The type of service being mocked.</typeparam>
        /// <param name="serviceContainer">The <see cref="IServiceContainer"/> that will handle the mocked service request.</param>
        /// <returns><see cref="Mock{T}"/></returns>
        public static Mock<T> StartMocking<T>(this IServiceContainer serviceContainer) where T : class
        {
            return StartMocking<T>(serviceContainer, null);
        }

        /// <summary>
        /// Creates a new <see cref="Mock{T}"/> instance and instructs the <see cref="IServiceContainer"/> 
        /// to return the <see cref="Mock.Object"/> instead of the actual service instance.
        /// </summary>
        /// <typeparam name="T">The type of service being mocked.</typeparam>
        /// <param name="serviceContainer">The <see cref="IServiceContainer"/> responsible for creating the mocked service.</param>
        /// <param name="serviceName">The name of the service being mocked.</param>
        /// <returns><see cref="Mock{T}"/></returns>
        public static Mock<T> StartMocking<T>(this IServiceContainer serviceContainer, string serviceName) where T : class
        {
            InstallMockPreProcessor(serviceContainer);

            return CreateMock<T>(serviceName);
        }

        private static Mock<T> CreateMock<T>(string serviceName) where T : class
        {
            var mock = new Mock<T>();
            var mockServiceInfo = new MockServiceInfo();
            mockServiceInfo.Mock = mock;
            mockServiceInfo.ServiceName = string.IsNullOrEmpty(serviceName) ? null : serviceName;
            mockServiceInfo.ServiceType = typeof(T);
            _mockedServices.Add(mockServiceInfo);
            return mock;
        }

        private static void InstallMockPreProcessor(IServiceContainer serviceContainer)
        {
            if (!serviceContainer.PreProcessors.Any(p => p.GetType() == typeof(MockPreprocessor)))
                serviceContainer.PreProcessors.Add(new MockPreprocessor(_mockedServices));
        }


        /// <summary>
        /// Stops mocking the requested service type.
        /// </summary>
        /// <typeparam name="T">The service type currently being mocked.</typeparam>
        /// <param name="serviceContainer">The <see cref="IServiceContainer"/> currenly responsible for creating the mocked service.</param>        
        public static void StopMocking<T>(this IServiceContainer serviceContainer) where T : class
        {
            StopMocking<T>(serviceContainer, null);
        }


        /// <summary>
        /// Stops mocking the requested service type.
        /// </summary>
        /// <typeparam name="T">The service type currently being mocked.</typeparam>
        /// <param name="serviceContainer">The <see cref="IServiceContainer"/> currenly responsible for creating the mocked service.</param>
        /// <param name="serviceName">The name of the service curretly being mocked.</param>
        public static void StopMocking<T>(this IServiceContainer serviceContainer, string serviceName) where T : class
        {
            serviceName = string.IsNullOrEmpty(serviceName) ? null : serviceName;
            var mockServiceInfo =
                _mockedServices.Where(ms => ms.ServiceType == typeof(T) && ms.ServiceName == serviceName).
                    FirstOrDefault();

            if (mockServiceInfo != null)
                _mockedServices.Remove(mockServiceInfo);
        }

        /// <summary>
        /// Stops mocking all services.
        /// </summary>
        /// <param name="serviceContainer">The <see cref="IServiceContainer"/> responsible for creating mocked services.</param>
        public static void StopMocking(this IServiceContainer serviceContainer)
        {
            _mockedServices.Clear();
        }

    }

}
